// SPDX-License-Identifier: MIT
// Copyright (C) 2018-present iced project and contributors

using System;
using System.IO;

namespace Generator.IO {
	sealed class FileWriter : IDisposable {
		readonly TextWriter writer;
		readonly TargetLanguage targetLanguage;
		readonly string numberByteFormat;
		readonly string singleLineCommentPrefix;
		readonly (string begin, string middle, string end) multiLineComment;
		bool needSpace;
		bool needIndent;
		int indentCount;
		string indentString;

		public FileWriter(TargetLanguage targetLanguage, TextWriter writer) {
			this.writer = writer;
			this.targetLanguage = targetLanguage;

			switch (targetLanguage) {
			case TargetLanguage.CSharp:
			case TargetLanguage.Rust:
			case TargetLanguage.RustJS:
				numberByteFormat = "0x{0:X2}";
				singleLineCommentPrefix = "// ";
				multiLineComment = ("", "// ", "");
				break;

			case TargetLanguage.Java:
				numberByteFormat = "(byte)0x{0:X2}";
				singleLineCommentPrefix = "// ";
				multiLineComment = ("", "// ", "");
				break;

			case TargetLanguage.Other:
			case TargetLanguage.Python:
				numberByteFormat = "0x{0:X2}";
				singleLineCommentPrefix = "# ";
				multiLineComment = ("", "# ", "");
				break;

			case TargetLanguage.Lua:
				numberByteFormat = "0x{0:X2}";
				singleLineCommentPrefix = "-- ";
				multiLineComment = ("", "-- ", "");
				break;

			default:
				throw new InvalidOperationException();
			}

			indentString = null!;
			needIndent = true;
			InitializeIndent();
		}

		void InitializeIndent() => indentString = GetIndentString(indentCount);

		static string GetIndentString(int indentCount) =>
			indentCount switch {
				1 => "\t",
				2 => "\t\t",
				3 => "\t\t\t",
				4 => "\t\t\t\t",
				5 => "\t\t\t\t\t",
				6 => "\t\t\t\t\t\t",
				7 => "\t\t\t\t\t\t\t",
				8 => "\t\t\t\t\t\t\t\t",
				_ => new string('\t', indentCount),
			};

		public readonly struct Indenter : IDisposable {
			readonly FileWriter writer;
			readonly int indent;

			public Indenter(FileWriter writer, int indent) {
				this.writer = writer;
				this.indent = indent;
				writer.IndentPrivate(indent);
			}

			public void Dispose() => writer.UnindentPrivate(indent);
		}

		public Indenter Indent() => new(this, 1);
		public Indenter Indent(int indent) => new(this, indent);

		void IndentPrivate(int indent) {
			if (indent < 0)
				throw new ArgumentOutOfRangeException(nameof(indent));
			indentCount += indent;
			InitializeIndent();
		}

		void UnindentPrivate(int indent) {
			if (indent < 0)
				throw new ArgumentOutOfRangeException(nameof(indent));
			indentCount -= indent;
			if (indentCount < 0)
				throw new InvalidOperationException();
			InitializeIndent();
		}

		void WriteIndent() {
			if (needIndent) {
				needIndent = false;
				writer.Write(indentString);
			}
		}

		static readonly string[] licenseHeader = new string[] {
			"SPDX-License-Identifier: MIT",
			"Copyright (C) 2018-present iced project and contributors",
		};
		static readonly string[] generatorFullFileMessage = new string[] {
			"⚠️This file was generated by GENERATOR!🦹‍♂️",
		};
		static readonly string[] generatorPartialFileMessage = new string[] {
			"⚠️This was generated by GENERATOR!🦹‍♂️",
		};

		void WriteCLangMultiLineComment(string[] lines) {
			if (multiLineComment.begin != string.Empty)
				WriteLine(multiLineComment.begin);
			foreach (var line in lines) {
				if (line == string.Empty)
					WriteLine(multiLineComment.middle.Trim());
				else {
					Write(multiLineComment.middle);
					WriteLine(line);
				}
			}
			if (multiLineComment.end != string.Empty)
				WriteLine(multiLineComment.end);
		}

		void WriteCLangSingleLineComment(string[] lines) {
			foreach (var msg in lines)
				WriteLine($"{singleLineCommentPrefix}{msg}");
		}

		void WriteCLangHeader() {
			WriteCLangMultiLineComment(licenseHeader);
			WriteLine();
			WriteCLangSingleLineComment(generatorFullFileMessage);
			WriteLine();
		}

		public void WritePartialGeneratedComment() => WriteCLangSingleLineComment(generatorPartialFileMessage);

		public void WriteFileHeader() {
			WriteCLangHeader();
			if (targetLanguage == TargetLanguage.CSharp) {
				WriteLine("#nullable enable");
				WriteLine();
			}
		}

		public void WriteLine(string s) {
			Write(s);
			WriteLine();
		}

		public void WriteByte(byte value) => WriteNumberComma(string.Format(numberByteFormat, value));

		void WriteNumberComma(string number) {
			if (needSpace)
				Write(" ");
			needSpace = true;
			Write(number);
			Write(",");
		}

		public void WriteCommentLine(string s) {
			Write(singleLineCommentPrefix);
			Write(s);
			WriteLine();
		}

		public void Write(string s) {
			WriteIndent();
			WriteNoIndent(s);
		}

		public void WriteLine() {
			writer.WriteLine();
			needSpace = false;
			needIndent = true;
		}

		public void WriteNoIndent(string s) => writer.Write(s);

		public void WriteLineNoIndent(string s) {
			WriteNoIndent(s);
			WriteLine();
		}

		public void Dispose() => writer.Dispose();
	}
}
